package vectors;

import Exceptions.VectorIndexOutOfBoundsException;
import Exceptions.IncompatibleVectorSizesException;
import java.util.*;
import java.io.*;

public class LinkedListVector implements VectorIn, Serializable {

    private int length;
    private LinkList head;
    private ArrayList observer;

    public LinkedListVector(int length) {
        this.length = length;
        observer = new ArrayList();

    }

    public LinkedListVector() {
        this.length = 0;
        this.head = null;
        observer = new ArrayList();
    }

    public LinkedListVector(Double... e) {
        for (Double elem : e) {
            add(elem);
            observer = new ArrayList();
        }
    }

    @Override
    public int getSize() {
        return this.length;
    }

    @Override
    public void setElement(double value, int index) {
        if (index < 0 || index > length) {
            throw new VectorIndexOutOfBoundsException();
        }
        LinkList list = head;
        for (int i = 0; i < length; i++) {
            if (i == index) {
                list.setElement(value);
                return;
            }
            list = list.getNext();
        }
        elementNotifyObservers(index);
    }

    @Override
    public Double getElement(int index) {
        if (index < 0 || index > length) {
            throw new VectorIndexOutOfBoundsException();
        }
        LinkList list = head;
        for (int i = 0; i < length; i++) {
            if (i == index) {
                return list.getElement();
            }
            list = list.getNext();
        }
        return null;
    }

    @Override
    public void massiveToVector(double[] massive) {
        removeAll();
        for (int i = 0; i < massive.length; i++) {
            add(massive[i]);
            updateNotifyObservers();
        }
    }

    @Override
    public void objectToVector(VectorIn List1) {
        removeAll();
        for (int i = 0; i < List1.getSize(); i++) {
            add(List1.getElement(i));
            updateNotifyObservers();
        }
    }

    @Override
    public void multiplyVector(double multiply) {

        LinkList list = head;
        for (int i = 0; i < length; i++) {
            list.setElement(list.getElement() * multiply);
            list = list.getNext();
            updateNotifyObservers();
        }
    }

    @Override
    public boolean equalsVectors(VectorIn List1) {
        LinkList list = head;
        if (List1.getSize() != length) {
            return false;
        }
        for (int i = 0; i < length; i++) {
            if (list.getElement() != List1.getElement(i)) {
                return false;
            }
            list = list.getNext();
        }
        return true;
    }

    @Override
    public boolean equals(Object obj) {
        LinkList list = head;
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof VectorIn)) {
            return false;
        }
        VectorIn vec = (VectorIn) obj;

        if (this.length != vec.getSize()) {
            return false;
        }

        for (int i = 0; i < this.length; i++) {
            if (list.getElement() != vec.getElement(i)) {
                return false;
            }
            list = list.getNext();
        }
        return true;
    }

    @Override
    public void vectorPlusVector(VectorIn List) throws IncompatibleVectorSizesException {
        LinkList list = head;
        if (length != List.getSize()) {
            throw new IncompatibleVectorSizesException();
        } else {
            for (int i = 0; i < length && i < List.getSize(); i++) {
                list.setElement(list.getElement() + List.getElement(i));
                list = list.getNext();
                updateNotifyObservers();
            }
        }
    }

    public void add(double element) {
        LinkList list;
        if (head == null) {
            list = new LinkList(element);
            list.setNext(list);
            list.setPrev(list);
            head = list;
        } else {
            list = new LinkList(element, head, head.getPrev());
            list.getPrev().setNext(list);
            list.getNext().setPrev(list);

        }
        length++;
        updateNotifyObservers();
    }

    public Double remove(int index) {
        LinkList list = head;
        for (int i = 0; i < length; i++) {
            if (i == index) {
                list.getPrev().setNext(list.getNext());
                list.getNext().setPrev(list.getPrev());
                length--;
                if (i == 0) {
                    head = head.getNext();
                }
                return list.getElement();
            }
            list = list.getNext();
        }
        updateNotifyObservers();
        return null;
    }

    public void removeAll() {
        head = null;
        length = 0;
        updateNotifyObservers();
    }

    @Override
    public String toString() {
        LinkList list = head;
        StringBuffer str = new StringBuffer();
        for (int i = 0; i < length; i++) {
            str.append(list.getElement()).append("\n");
            list = list.getNext();
        }
        return str.toString();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        LinkedListVector result;
        LinkList list = head;
        result = (LinkedListVector) super.clone();
        for (int i = 0; i < length; i++) {
            result.setElement(list.getElement(), i);
            list = (LinkList) list.getNext();
        }
        return result;
    }

    @Override
    public int hashCode() {
        int result = 0;
        for (int i = 0; i < length; i++) {
            long bits = Double.doubleToRawLongBits(i);
            result ^= ((int) (bits & 0x00000000FFFFFFFFL))
                    ^ ((int) ((bits & 0xFFFFFFFF00000000L) >> 32));
        }
        return result;
    }

    public Iterator<Double> iterator() {
        return new Iterator<Double>() {
            public int elem = 0;

            @Override
            public void remove() {
            }

            @Override
            public Double next() {
                return getElement(elem++);
            }

            @Override
            public boolean hasNext() {
                if (elem < length) {
                    return true;
                } else {
                    return false;
                }
            }
        };
    }

    @Override
    public void registerObserver(Observer o) {
        observer.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i = observer.indexOf(o);
        if (i >= 0) {
            observer.remove(i);
        }
    }

    @Override
    public void elementNotifyObservers(int index) {
        for (int i = 0; i < observer.size(); i++) {
            Observer observers = (Observer) observer.get(i);
            observers.updateElement(index);
        }
    }

    public void updateNotifyObservers() {
        for (int i = 0; i < observer.size(); i++) {
            Observer observers = (Observer) observer.get(i);
            observers.update();
        }
    }

    private class LinkList implements Serializable {

        private LinkList prev;
        private LinkList next;
        private double element;

        public LinkList(double element) {
            this.element = element;
            this.next = null;
            this.prev = null;
        }

        public LinkList(double element, LinkList next, LinkList prev) {
            this.element = element;
            this.next = next;
            this.prev = prev;
        }

        public double getElement() {
            return this.element;
        }

        public void setElement(double element) {
            this.element = element;
        }

        public LinkList getNext() {
            return this.next;
        }

        public LinkList getPrev() {
            return this.prev;
        }

        public LinkList getHead() {
            return head;
        }

        public void setNext(LinkList next) {
            this.next = next;
        }

        public void setPrev(LinkList prev) {
            this.prev = prev;
        }
    }
}
